package com.xieyong.mywebsite.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * MD5 工具类，提供字符串和文件的 MD5 计算功能
 */
public class Md5Util {
    private static final String ALGORITHM = "MD5";
    private static final String CHARSET = "UTF-8";
    private static final char[] HEX_DIGITS = {
            '0', '1', '2', '3', '4', '5', '6', '7',
            '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
    };

    /**
     * 计算字符串的 MD5 值
     * @param str 待计算的字符串
     * @return MD5 值的 16 进制字符串表示
     */
    public static String md5(String str) {
        if (str == null) {
            return null;
        }
        try {
            byte[] bytes = str.getBytes(CHARSET);
            return md5(bytes);
        } catch (UnsupportedEncodingException e) {
            // 通常不会发生，UTF-8 是标准编码
            throw new RuntimeException(e);
        }
    }

    /**
     * 计算字节数组的 MD5 值
     * @param bytes 待计算的字节数组
     * @return MD5 值的 16 进制字符串表示
     */
    public static String md5(byte[] bytes) {
        if (bytes == null) {
            return null;
        }
        try {
            MessageDigest messageDigest = MessageDigest.getInstance(ALGORITHM);
            messageDigest.update(bytes);
            return bytesToHex(messageDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            // 通常不会发生，MD5 是标准算法
            throw new RuntimeException(e);
        }
    }

    /**
     * 计算文件的 MD5 值
     * @param file 待计算的文件
     * @return MD5 值的 16 进制字符串表示，若文件不存在或读取失败则返回 null
     */
    public static String md5(File file) {
        if (file == null || !file.exists() || !file.isFile()) {
            return null;
        }
        try (FileInputStream fis = new FileInputStream(file)) {
            MessageDigest messageDigest = MessageDigest.getInstance(ALGORITHM);
            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = fis.read(buffer)) != -1) {
                messageDigest.update(buffer, 0, bytesRead);
            }
            return bytesToHex(messageDigest.digest());
        } catch (NoSuchAlgorithmException | IOException e) {
            // 记录日志或处理异常
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 计算带盐值的 MD5 值
     * @param str 待计算的字符串
     * @param salt 盐值
     * @return 带盐值的 MD5 值的 16 进制字符串表示
     */
    public static String md5WithSalt(String str, String salt) {
        if (str == null) {
            return null;
        }
        if (salt == null) {
            salt = "";
        }
        return md5(str + salt);
    }

    /**
     * 将字节数组转换为 16 进制字符串
     * @param bytes 字节数组
     * @return 16 进制字符串
     */
    private static String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder(bytes.length * 2);
        for (byte b : bytes) {
            sb.append(HEX_DIGITS[(b >> 4) & 0x0f]);
            sb.append(HEX_DIGITS[b & 0x0f]);
        }
        return sb.toString();
    }

    /**
     * 验证字符串的 MD5 值
     * @param str 原始字符串
     * @param md5 待验证的 MD5 值
     * @return 如果匹配返回 true，否则返回 false
     */
    public static boolean verify(String str, String md5) {
        return md5 != null && md5.equals(md5(str));
    }

    /**
     * 验证带盐值的字符串的 MD5 值
     * @param str 原始字符串
     * @param salt 盐值
     * @param md5 待验证的 MD5 值
     * @return 如果匹配返回 true，否则返回 false
     */
    public static boolean verifyWithSalt(String str, String salt, String md5) {
        return md5 != null && md5.equals(md5WithSalt(str, salt));
    }

    /**
     * 验证文件的 MD5 值
     * @param file 文件
     * @param md5 待验证的 MD5 值
     * @return 如果匹配返回 true，否则返回 false
     */
    public static boolean verify(File file, String md5) {
        return md5 != null && md5.equals(md5(file));
    }
}    